#include "StdAfx.h"

#include "MathUtils/Ray.h"

#include "ColorUtils/Color.h"
#include "ColorUtils/Image.h"

#include "FirstIntersectionResult.h"
#include "SecondIntersectionResult.h"
#include "Intersectable.h"
#include "Light.h"
#include "Scene.h"
#include "World.h"
#include "Camera.h"

#include "PinholeCamera.h"

PinholeCamera::PinholeCamera(const Ray& ray, const Point& upDirection, double screenDist, double screenWidth, double screenHeight, unsigned samplingWidth) 
	: _pointAndDirection(ray), _upDirection(upDirection), _screenDist(screenDist), _screenWidth(screenWidth)
	, _screenHeight(screenHeight), _rightDirection(Direction(0, 0, 0)), _samplingWidth(samplingWidth)
{
	_rightDirection.X = upDirection.Y * _pointAndDirection.D.Z - upDirection.Z * _pointAndDirection.D.Y;
	_rightDirection.Y = upDirection.Z * _pointAndDirection.D.X - upDirection.X * _pointAndDirection.D.Z;
	_rightDirection.Z = upDirection.X * _pointAndDirection.D.Y - upDirection.Y * _pointAndDirection.D.X;

	_upDirection.X = _rightDirection.Y * _pointAndDirection.D.Z - _rightDirection.Z * _pointAndDirection.D.Y;
	_upDirection.Y = _rightDirection.Z * _pointAndDirection.D.X - _rightDirection.X * _pointAndDirection.D.Z;
	_upDirection.Z = _rightDirection.X * _pointAndDirection.D.Y - _rightDirection.Y * _pointAndDirection.D.X;
}

PinholeCamera::PinholeCamera(const Ray& ray, const Point& upDirection, double halfUpDownFrustum, double halfLeftRightFrustum, unsigned samplingWidth) 
	: _pointAndDirection(ray), _upDirection(upDirection), _screenDist(1), _screenWidth(tan(halfLeftRightFrustum) * 2.0), _screenHeight(tan(halfUpDownFrustum) * 2.0), _samplingWidth(samplingWidth)
	, _rightDirection(Direction(_upDirection.Y * _pointAndDirection.D.Z - _upDirection.Z * _pointAndDirection.D.Y
		, _upDirection.Z * _pointAndDirection.D.X - _upDirection.X * _pointAndDirection.D.Z
		, _upDirection.X * _pointAndDirection.D.Y - _upDirection.Y * _pointAndDirection.D.X))
{
	_rightDirection.X = upDirection.Y * _pointAndDirection.D.Z - upDirection.Z * _pointAndDirection.D.Y;
	_rightDirection.Y = upDirection.Z * _pointAndDirection.D.X - upDirection.X * _pointAndDirection.D.Z;
	_rightDirection.Z = upDirection.X * _pointAndDirection.D.Y - upDirection.Y * _pointAndDirection.D.X;

	_upDirection.X = _rightDirection.Y * _pointAndDirection.D.Z - _rightDirection.Z * _pointAndDirection.D.Y;
	_upDirection.Y = _rightDirection.Z * _pointAndDirection.D.X - _rightDirection.X * _pointAndDirection.D.Z;
	_upDirection.Z = _rightDirection.X * _pointAndDirection.D.Y - _rightDirection.Y * _pointAndDirection.D.X;
}

void PinholeCamera::Init(World_ptr world)
{
	_world = world;
}

void PinholeCamera::Draw(IplImage* img, unsigned* counter)
{
	double widthPixelSize = _screenWidth / img->width;
	double heightPixelSize = _screenHeight / img->height;
	
	Point garbagePoint(0, 0, 0), midleOfScreen(_pointAndDirection.D);
	midleOfScreen *= _screenDist;
	midleOfScreen += _pointAndDirection.P;

	SecondIntersectionResult result, sampligResult;

	result.Width = img->width;
	result.Height = img->height;

	sampligResult.Width = img->width;
	sampligResult.Height = img->height;

	unsigned pixelCount = 0;
	for (int y = -img->height / 2; y < img->height / 2; y++, pixelCount += (img->widthStep - img->width * 3))
	{
		for (int x = img->width / 2; x > -img->width / 2; x--, pixelCount += 3)
		{
			if (counter != NULL)
				(*counter)++;

			result.SetCoordinates(x, y);
			result.ColorAtPoint = Point(0, 0, 0);

			sampligResult.SetCoordinates(x, y);

			Point tempPoint = midleOfScreen + _rightDirection * (x * widthPixelSize) + _upDirection * (y * heightPixelSize);

			Ray tempRay(_pointAndDirection);
			tempRay.D.Set(tempPoint - _pointAndDirection.P);

			for (int i = 0; i < (int)_samplingWidth; i++)
			{
				for (int j = 0; j < (int)_samplingWidth; j++)
				{
					Point tempPoint = midleOfScreen + _rightDirection * ((x - 0.5 + 1.0 / (_samplingWidth * 2) + (double)i / _samplingWidth) * widthPixelSize)
						+ _upDirection * ((y - 0.5 + 1.0 / (_samplingWidth * 2) + (double)j / _samplingWidth) * heightPixelSize);

					Ray tempRay(_pointAndDirection);
					tempRay.D.Set(tempPoint - _pointAndDirection.P);

					_world->WhereIntersects(tempRay, sampligResult);
					_world->ColorAtIntersection(sampligResult, garbagePoint, tempRay);
					result.ColorAtPoint += sampligResult.ColorAtPoint;
				}
			}
			result.ColorAtPoint *= 1.0 / ((_samplingWidth * _samplingWidth)); 


			img->imageData[pixelCount] = (char)std::max(std::min((int)floor(result.ColorAtPoint.Z * 255.0), 255), 0);
			img->imageData[pixelCount + 1] = (char)std::max(std::min((int)floor(result.ColorAtPoint.Y * 255.0), 255), 0);
			img->imageData[pixelCount + 2] = (char)std::max(std::min((int)floor(result.ColorAtPoint.X * 255.0), 255), 0);
		}
	}

}

PinholeCamera::~PinholeCamera(void){}
